﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media.Media3D;
using PDBSmartMVVM.model;
using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using SaveFileDialog = Microsoft.Win32.SaveFileDialog;

namespace PDBSmartMVVM.viewmodel
{
    /// <summary>
    /// The ViewModel for the application's main window.
    /// </summary>
    public class MainWindowViewModel : WorkspaceViewModel
    {
        #region Fields

        ReadOnlyCollection<CommandViewModel> _commands;
        ObservableCollection<WorkspaceViewModel> _workspaces;
        private RelayCommand _openPdbCommand;
        private RelayCommand _newFileDropCommand;
        private ObservableCollection<object> _objects;
        public ObservableCollection<object> Objects
        {
            get { return _objects; }
            set
            {
                if (value != _objects)
                {
                    _objects = value;
                    OnPropertyChanged("Objects");
                }
            }
        }

        private RelayCommand _calculateCommand;

        #endregion // Fields

        #region Constructor

        public MainWindowViewModel()
        {
            base.DisplayName = "3D";
            this.Objects = new ObservableCollection<object>();
        }

        #endregion // Constructor

        #region Commands

        /// <summary>
        /// Returns a read-only list of commands 
        /// that the UI can display and execute.
        /// </summary>
        public ReadOnlyCollection<CommandViewModel> Commands
        {
            get
            {
                if (_commands == null)
                {
                    List<CommandViewModel> cmds = this.CreateCommands();
                    _commands = new ReadOnlyCollection<CommandViewModel>(cmds);
                }
                return _commands;
            }
        }

        List<CommandViewModel> CreateCommands()
        {
            return new List<CommandViewModel>();
        }

        #endregion // Commands

        #region Workspaces

        /// <summary>
        /// Returns the collection of available workspaces to display.
        /// A 'workspace' is a ViewModel that can request to be closed.
        /// </summary>
        public ObservableCollection<WorkspaceViewModel> Workspaces
        {
            get
            {
                if (_workspaces == null)
                {
                    _workspaces = new ObservableCollection<WorkspaceViewModel>();
                    _workspaces.CollectionChanged += this.OnWorkspacesChanged;
                }
                return _workspaces;
            }
        }

        void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count != 0)
                foreach (WorkspaceViewModel workspace in e.NewItems)
                    workspace.RequestClose += this.OnWorkspaceRequestClose;

            if (e.OldItems != null && e.OldItems.Count != 0)
                foreach (WorkspaceViewModel workspace in e.OldItems)
                    workspace.RequestClose -= this.OnWorkspaceRequestClose;
        }

        void OnWorkspaceRequestClose(object sender, EventArgs e)
        {
            WorkspaceViewModel workspace = sender as WorkspaceViewModel;
            if (workspace.GetType() == typeof(TreeViewModel))
            {
                foreach (var model in ((TreeViewModel)workspace).Models)
                {
                    foreach (AminoTreeViewModel amino in model.Children)
                    {
                        foreach (AtomViewModel atom in amino.Children)
                        {
                            Objects.Remove(atom.Atom.ModelUiElement3D);
                        }
                        foreach (var bond in amino.ChemicalBondsCollection)
                        {
                            Objects.Remove(bond);
                        }
                    }
                    foreach (var chAmino in model.CheckedAminos)
                    {
                        Objects.Remove(chAmino.Value);
                    }
                }
            }
            workspace.Dispose();
            this.Workspaces.Remove(workspace);
        }

        #endregion // Workspaces

        #region Private Helpers


        void SetActiveWorkspace(WorkspaceViewModel workspace)
        {
            Debug.Assert(this.Workspaces.Contains(workspace));

            ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Workspaces);
            if (collectionView != null)
                collectionView.MoveCurrentTo(workspace);
        }

        #endregion // Private Helpers

        #region RelayCommands

        public ICommand NewFileDropCommand
        {
            get { return _newFileDropCommand ?? (_newFileDropCommand = new RelayCommand(NewFileDropCommandExecute)); }
        }

        private void NewFileDropCommandExecute(object o)
        {
            var dto = o as System.Windows.DataObject;
            if (null == dto) return;
            TreeViewModel treeModels = null;

            foreach (var pdbfileName in dto.GetFileDropList())
            {
                if (Path.GetExtension(pdbfileName).ToLower(CultureInfo.CurrentCulture) != ".pdb")
                {
                    continue;
                }
                var models = ParsePdbFile(pdbfileName);
                treeModels = new TreeViewModel(models, Path.GetFileNameWithoutExtension(pdbfileName), Objects);
                this.Workspaces.Add(treeModels);
            }
            if(treeModels != null)
                SetActiveWorkspace(treeModels);
        }

        public ICommand OpenPdbCommand
        {
            get { return _openPdbCommand ?? (_openPdbCommand = new RelayCommand(param => OpenPdbCommandExecute())); }
        }

        private void OpenPdbCommandExecute()
        {

            var dialog = new OpenFileDialog { InitialDirectory = @"C:\Users\Пользователь\Documents\Visual Studio 2012\Projects\PDBSmart",
            Multiselect = true};
            if (dialog.ShowDialog() == true)
            {
                TreeViewModel treeModels = null;
                foreach (var pdbfileName in dialog.FileNames)
                {
                    var models = ParsePdbFile(pdbfileName);
                    treeModels = new TreeViewModel(models, Path.GetFileNameWithoutExtension(pdbfileName), Objects);
                    this.Workspaces.Add(treeModels);
                }
                SetActiveWorkspace(treeModels);

            }
        }

        private Dictionary<int, List<Amino>> ParsePdbFile(string sourceFileName)
        {
            #region parse PDB file
            var models = new Dictionary<int, List<Amino>>();

            using (var reader = new StreamReader(sourceFileName))
            {
                string line;
                int modelNumber = 1;
                int atomNumber = 1;
                int atomInAminoNumber = 1;

                int aminoNumber = -1;
                models.Add(modelNumber, new List<Amino>());

                while ((line = reader.ReadLine()) != null)
                {
                    if (line.StartsWith("MODEL"))
                    {
                        if (modelNumber != 1) models.Add(modelNumber, new List<Amino>());
                        atomNumber = 1;
                        aminoNumber = -1;
                        continue;
                    }
                    if (line.StartsWith("ENDMDL"))
                    {
                        modelNumber++;
                        continue;
                    }

                    if (line.StartsWith("ATOM"))
                    {
                        AtomType atomType;
                        if (Enum.TryParse(line.Substring(13, 4), out atomType))
                        {

                            AminoType aminoType;
                            if (Enum.TryParse(line.Substring(17, 3), out aminoType))
                            {
                                var culture = new NumberFormatInfo() {NumberDecimalSeparator = ".", NegativeSign = "-"};
                                var point =
                                    new Point3D(
                                        Convert.ToDouble(line.Substring(30, 8).Replace(" ", default(string)), culture),
                                        Convert.ToDouble(line.Substring(38, 8).Replace(" ", default(string)), culture),
                                        Convert.ToDouble(line.Substring(46, 8).Replace(" ", default(string)), culture));
                                if (atomType == AtomType.N)
                                {
                                    models[modelNumber].Add(new Amino()
                                        {
                                            Atoms =
                                                new Hashtable()
                                                    {
                                                        {atomType, new Atom(atomInAminoNumber, point, atomType)}
                                                    },
                                            Number = Convert.ToInt32(line.Substring(23, 4).Replace(" ", default(string))),
                                            AminoType = aminoType,
                                            Index = ++aminoNumber
                                        });

                                }
                                else
                                {
                                    models[modelNumber][aminoNumber].Atoms.Add(atomType,
                                                                               new Atom(atomInAminoNumber, point,
                                                                                        atomType));
                                }
                            }
                        }
                        atomNumber++;
                    }
                }
            }

            #endregion

            return models;
        }

        public ICommand CalculateCommand
        {
            get
            {
                return _calculateCommand ?? (_calculateCommand = new RelayCommand(param => CalculatedCommandExecute()));
            }
        }

        private void CalculatedCommandExecute()
        {
            var sb = new StringBuilder();
            var angles = new Dictionary<int, List<Angles>>();
            foreach (var vm in Workspaces)
            {
                if (vm.GetType() != typeof(TreeViewModel))
                    continue;
                foreach (var viewmodel in ((TreeViewModel)vm).Models)
                {
                    Model model = viewmodel.Model;
                    var aminos = viewmodel.Children.Where(s => s.IsCalculated)
                                           .Cast<AminoTreeViewModel>()
                                           .Select(s => s.Amino)
                                           .ToArray();
                    if (aminos.Length < 1)
                        continue;
                    angles.Add(model.Number, new List<Angles>());
                    const string format = "{0, -3} {1, -7} {2, -10}\n";
                    //sb.Append(string.Format("[Model #{0}]\n", model.Number));

                    int count = 0;
                    foreach (var amino in aminos)
                    {
                        var prevamino = model.Aminos.FirstOrDefault(s => s.Index == amino.Index - 1);
                        var nextamino = model.Aminos.FirstOrDefault(s => s.Index == amino.Index + 1);
                        double? phi = null, psi = null, omega = null;
                        //sb.Append(string.Format("\tphi\tpsi\n"));

                        if (prevamino != null && nextamino != null)
                        {
                            phi = Utils.Tors(((Atom) prevamino.Atoms[AtomType.C]).Point,
                                             ((Atom) amino.Atoms[AtomType.N]).Point,
                                             ((Atom) amino.Atoms[AtomType.CA]).Point,
                                             ((Atom) amino.Atoms[AtomType.C]).Point);
                            psi = Utils.Tors(((Atom) amino.Atoms[AtomType.N]).Point,
                                             ((Atom) amino.Atoms[AtomType.CA]).Point,
                                             ((Atom) amino.Atoms[AtomType.C]).Point,
                                             ((Atom) nextamino.Atoms[AtomType.N]).Point);
                            omega = Utils.Tors(((Atom)amino.Atoms[AtomType.CA]).Point,
                                             ((Atom)amino.Atoms[AtomType.C]).Point,
                                             ((Atom)amino.Atoms[AtomType.N]).Point,
                                             ((Atom)nextamino.Atoms[AtomType.C]).Point); 
                            sb.Append(string.Format(format, model.Number, amino.Number*3-2, phi));
                            sb.Append(string.Format(format, model.Number, amino.Number * 3 - 1, psi));
                            sb.Append(string.Format(format, model.Number, amino.Number*3, omega));

                        }
                        else
                        {
                            if (nextamino == null && aminos.Length>1)
                            {
                                phi = Utils.Tors(
                                    ((Atom) aminos[aminos.Length - 2].Atoms[AtomType.C]).Point,
                                    ((Atom) amino.Atoms[AtomType.N]).Point,
                                    ((Atom) amino.Atoms[AtomType.CA]).Point,
                                    ((Atom) amino.Atoms[AtomType.C]).Point);
                                sb.Append(string.Format(format, model.Number, amino.Number * 3 - 2, phi));
                                sb.Append(string.Format(format, model.Number, amino.Number * 3 - 1, "-"));
                                sb.Append(string.Format(format, model.Number, amino.Number * 3, "-"));
                            }
                            else
                            {
                                if (prevamino == null && aminos.Length>1)
                                {
                                    psi = Utils.Tors(
                                        ((Atom)amino.Atoms[AtomType.N]).Point,
                                        ((Atom)amino.Atoms[AtomType.CA]).Point,
                                        ((Atom)amino.Atoms[AtomType.C]).Point,
                                        ((Atom)aminos[1].Atoms[AtomType.N]).Point);
                                    omega = Utils.Tors(((Atom)amino.Atoms[AtomType.CA]).Point,
                                            ((Atom)amino.Atoms[AtomType.C]).Point,
                                            ((Atom)amino.Atoms[AtomType.N]).Point,
                                            ((Atom)aminos[1].Atoms[AtomType.C]).Point);
                                    sb.Append(string.Format(format, model.Number, amino.Number * 3 - 2, "-"));
                                    sb.Append(string.Format(format, model.Number, amino.Number * 3 - 1, psi));
                                    sb.Append(string.Format(format, model.Number, amino.Number * 3, omega));
                                }
                            }
                        }
                        angles[model.Number].Add(new Angles() { PhiNumber = amino.Number * 3 - 2, PsiNumber = amino.Number * 3 - 1, Phi = phi, Psi = psi });
                        count++;
                        //Debug.Write(sb);
                    }
                }
                sb.Append("[Differences]\n");
                if (angles.Keys.Count > 1)
                {
                    const string format = "{0, -3} {1, -10}\n";
                    var collection = angles.Take(2).ToArray();
                    foreach (var angle in collection[0].Value)
                    {
                        var phinext = collection[1].Value.FirstOrDefault(s => s.PhiNumber == angle.PhiNumber);
                        if (phinext != null && angle.Phi != null && phinext.Phi != null)
                        {
                            var phiDifferent = Utils.CirclarDistance(angle.Phi.Value, phinext.Phi.Value);
                            sb.Append(string.Format(format, angle.PhiNumber, phiDifferent));
                        }
                        else
                        {
                            sb.Append(string.Format(format, angle.PhiNumber, "-"));
                        }

                        var psinext = collection[1].Value.FirstOrDefault(s => s.PsiNumber == angle.PsiNumber);
                        if (psinext != null && angle.Psi != null && psinext.Psi != null)
                        {
                            var psiDifferent = Utils.CirclarDistance(angle.Psi.Value, psinext.Psi.Value);
                            sb.Append(string.Format(format, angle.PsiNumber, psiDifferent));
                        }
                        else
                        {
                            sb.Append(string.Format(format, angle.PsiNumber, "-"));
                        }
                    }
                }
            }
            

            var saveFileDialog1 = new SaveFileDialog { RestoreDirectory = true };
            try
            {
                if (saveFileDialog1.ShowDialog() == true)
                {
                    using (var s = new StreamWriter(saveFileDialog1.OpenFile()))
                    {
                        s.Write(sb);
                        s.Close();
                    }
                    MessageBox.Show("Done!");
                }
            }
            catch (Exception exception)
            {
                MessageBox.Show("Error data saving");
            }

        }

        #endregion
    }

    public class Angles
    {
        public int PhiNumber { get; set; }
        public int PsiNumber { get; set; }

        public double? Phi { get; set; }
        public double? Psi { get; set; }
    }
}
